using System;
using System.IO;
using System.Runtime.CompilerServices;

namespace Pinwheel.LSerialize
{
    public static unsafe partial class LSerializer
    {
        public static int PackSeg(byte* src, byte* dst, int n)
        {
            byte header = 0;
            int notzero = 0;

            byte* header_ptr = dst;
            ++dst;
            
            for (int i = 0; i < 8; i++)
            {
                if (src[i] != 0)
                {
                    notzero++;
                    header |= (byte) (1 << i);
                    var val = src[i];
                    *dst = val;
                    ++dst;
                }
            }
            
            if ((notzero == 7 || notzero == 6) && n > 0) {
                notzero = 8;
            }
            
            if (notzero == 8) {
                if (n > 0) {
                    return 8;
                } else {
                    return 10;
                }
            }
            
            if (null != header_ptr) 
                *header_ptr = header;
            
            return notzero + 1;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void write_ff(byte* src, byte* dst, int n)
        {
            dst[0] = 0xff;
            dst[1] = (byte)(n - 1);
            Native.Utils.Copy(dst + 2, src, n * 8);
        }

        public static int Pack(LStreamBuffer* src, LStreamBuffer* dst)
        {
            src->Seek(0, SeekOrigin.Begin);
            dst->Seek(0, SeekOrigin.Begin);
            
            byte* ff_srcstart = null;
            byte* ff_desstart = null;
            int ff_n = 0;
            int size = 0;
            
            if (src->Size < 8) src->EnsureSize(8);
            for (int i = 0; i < src->Size; i += 8)
            {
                dst->EnsureSize(10);
                int n = PackSeg(src->Current, dst->Current, ff_n);
                if (n == 10)
                {
                    // first FF
                    ff_srcstart = src->Current;
                    ff_desstart = dst->Current;
                    ff_n = 1;
                }
                else if (n == 8 && ff_n > 0)
                {
                    ++ff_n;
                    if (ff_n == 256)
                    {
                        write_ff(ff_srcstart, ff_desstart, ff_n);
                        dst->Flush();
                        ff_n = 0;
                    }
                }
                else
                {
                    if (ff_n > 0)
                    {
                        write_ff(ff_srcstart, ff_desstart, ff_n);
                        dst->Flush();
                        ff_n = 0; 
                    }
                }
                src->Seek(8, SeekOrigin.Current);
                dst->Seek(n, SeekOrigin.Current);
                dst->Flush();
                size += n;
            }

            if (ff_n > 0)
                write_ff(ff_srcstart, ff_desstart, ff_n);
            
            dst->Flush();
            return size;
        }
        
        public static int Unpack(LStreamBuffer* src, LStreamBuffer* dst)
        {
            src->Seek(0, SeekOrigin.Begin);
            dst->Seek(0, SeekOrigin.Begin);
            
            int size = 0;
            while (src->Free > 0)
            {
                byte header = src->PopByte();
                if (header == 0xff)
                {
                    int n = (src->PopByte() + 1) * 8;
                    src->EnsureTail(n);
                    dst->EnsureSize(n);
                    Native.Utils.Copy(dst->Current, src->Current, n);
                    
                    src->Seek(n, SeekOrigin.Current);
                    dst->Seek(n, SeekOrigin.Current);
                    size += n;
                }
                else
                {
                    for (int i = 0; i < 8; i++)
                    {
                        int nz = (header >> i) & 1;
                        dst->PushByte(nz == 1 ? src->PopByte() : (byte)0);
                        ++size;
                    }
                }
            }
            return size; 
        }
    }
}
